package com.seuic.device_implement;

import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;

import com.seuic.android.PinModeEnum;
import com.seuic.android.PinpadEnterPinListener;
import com.seuic.android.TusnInfo;
import com.seuic.constants.PinpadConstants;
import com.seuic.posdsdk.device_interface.PinpadDevice;
import com.seuic.util.HexUtil;

import java.util.Arrays;

public class PinpadDeviceImp extends BaseImplement implements PinpadDevice {

    private static PinpadDeviceImp self;

    static PinpadDeviceImp getInstance() {
        if ( self == null ) {
            synchronized ( PinpadDeviceImp.class ) {
                if ( self == null ) {
                    self = new PinpadDeviceImp();
                }
            }
        }
        return self;
    }

    private static final int MAX_KEY_INDEX = 49;

    @Override
    public int downloadProtetKey(int index, String protectKey) {
        Log.d( LOG_TAG, "downloadProtetKey: " + index + ", " + protectKey );
        if ( superPinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return -1;
        }
        if ( null == protectKey ) {
            setLastError( "Input Param Error" );
            return -1;
        }
        int protect_key_index = getProtectKeyIndex( index );
        if ( protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX ) {
            Log.e( LOG_TAG, "protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX" );
            setLastError( "Protect Key Index Error" );
            return -2;
        }
        try {
            int result = superPinpad.DownloadPinMK( ( byte )protect_key_index, HexUtil.hexStringToByteArray(protectKey) );
            if ( result != 0 ) {
                Log.e( LOG_TAG, "DownloadPinMK Failed: " + result );
                setLastError( "Download Protect Key Error" );
                return -3;
            }
            result = superPinpad.DownloadMacMK( ( byte )protect_key_index, HexUtil.hexStringToByteArray(protectKey) );
            if ( result != 0 ) {
                Log.e( LOG_TAG, "DownloadMacMK Failed: " + result );
                setLastError( "Download Protect Key Error" );
                return -3;
            }
            result = superPinpad.DownloadDesMK( ( byte )protect_key_index, HexUtil.hexStringToByteArray(protectKey) );
            if ( result != 0 ) {
                Log.e( LOG_TAG, "DownloadDesMK Failed: " + result );
                setLastError( "Download Protect Key Error" );
                return -3;
            }
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return -4;
        }
        return 0;
    }

    private int getProtectKeyIndex( int index ) {
        //return 10 + index;
        return 10;
    }

    private int getMainKeyIndex( int index) {
        if ( index < 10 ) {
            return index;
        } else {
            return index + 1;
        }

    }

    @Override
    public int downloadMainkey(int index, String mainKey, String checkValue) {
        Log.d( LOG_TAG, "downloadMainkey: " + index + ", " + mainKey + ", " + checkValue );
        if ( superPinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return -1;
        }
        if ( mainKey == null || checkValue == null ) {
            Log.e( LOG_TAG, "mainKey == null || mainChk == null" );
            setLastError( "Input Param Error" );
            return -1;
        }
        int protect_key_index = getProtectKeyIndex( index );
        if ( protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX ) {
            Log.e( LOG_TAG, "protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX" );
            setLastError( "Protect Key Index Error" );
            return -2;
        }
        int main_key_index = getMainKeyIndex( index );
        if ( main_key_index < 0 || main_key_index > MAX_KEY_INDEX ) {
            Log.e( LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX" );
            setLastError( "Main Key Index Error" );
            return -2;
        }
        try {
            int result = superPinpad.DownloadEncMk( ( byte )protect_key_index, ( byte)main_key_index,
                                HexUtil.hexStringToByteArray( mainKey + checkValue ) );
            if ( 0 != result ) {
                Log.e( LOG_TAG, "DownloadEncMk Failed: " + result );
                setLastError( "Main Key Download Error" );
                return -3;
            }
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return -4;
        }
        return 0;
    }

    @Override
    public int downloadWorkKeys(int index, String pinKey, String pinKeyCheckValue, String macKey,
                                String macKeyCheckValue, String tdKey, String tdKeyCheckValue) {

        Log.d( LOG_TAG, "downloadWorkKeys: " + index + ", PinKey:" + pinKey + ", " + pinKeyCheckValue
                    + ", MacKey:" + macKey + ", " + macKeyCheckValue
                    + ", TDKey: " + tdKey + ", " + tdKeyCheckValue );
        if ( superPinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return -1;
        }
        if ( pinKey == null || pinKeyCheckValue == null ) {
            Log.e( LOG_TAG, "pinKey == null || pinKeyCheckValue == null" );
            setLastError( "Input Param Error" );
            return -1;
        }
        if ( macKey == null || macKeyCheckValue == null ) {
            Log.e( LOG_TAG, "macKey == null || macKeyCheckValue == null" );
            setLastError( "Input Param Error" );
            return -1;
        }
        if ( tdKey == null || tdKeyCheckValue == null ) {
            Log.e( LOG_TAG, "tdKey == null || tdKeyCheckValue == null" );
            setLastError( "Input Param Error" );
            return -1;
        }
        int main_key_index = getMainKeyIndex( index );
        if ( main_key_index < 0 || main_key_index > MAX_KEY_INDEX ) {
            Log.e( LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX" );
            setLastError( "MainKey Index Error" );
            return -2;
        }
        int work_key_index = main_key_index;
        try {
            int result = pinpad.DownloadPinK( ( byte )main_key_index, ( byte )work_key_index,
                            HexUtil.hexStringToByteArray( pinKey + pinKeyCheckValue) );
            if ( 0 != result ) {
                Log.e( LOG_TAG, "DownloadPinK Failed: " + result );
                setLastError( "Pin Key Download Error" );
                return -3;
            }
            result = pinpad.DownloadMacK( ( byte )main_key_index, ( byte )work_key_index,
                            HexUtil.hexStringToByteArray( macKey + macKeyCheckValue) );
            if ( 0 != result ) {
                Log.e( LOG_TAG, "DownloadMacK Failed: " + result );
                setLastError( "Mac Key Download Error" );
                return -3;
            }
            result = pinpad.DownloadDesK( ( byte )main_key_index, ( byte )work_key_index,
                            HexUtil.hexStringToByteArray( tdKey + tdKeyCheckValue) );
            if ( 0 != result ) {
                Log.e( LOG_TAG, "DownloadDesK Failed: " + result );
                setLastError( "Track Key Download Error" );
                return -3;
            }
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return -4;
        }
        return 0;
    }

    @Override
    public String macKeyEncrypt(int index, String msg) {
        if ( pinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray( msg );
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.MacKeyEncrypt( (byte)index, msg_byte, encrypt_byte );
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return null;
        }
        return HexUtil.byteToHexString( encrypt_byte );
    }

    @Override
    public String macKeyDecrypt(int index, String msg) {
        if ( pinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray( msg );
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.MacKeyDecrypt( (byte)index, msg_byte, encrypt_byte );
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return null;
        }
        return HexUtil.byteToHexString( encrypt_byte );
    }

    @Override
    public String trackKeyEncrypt(int index, String msg) {
        if ( pinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray( msg );
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.TrackKeyEncrypt( (byte)index, msg_byte, encrypt_byte );
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return null;
        }
        return HexUtil.byteToHexString( encrypt_byte );
    }

    @Override
    public String trackKeyDecrypt(int index, String msg) {
        if ( pinpad == null ) {
            setLastError( "Pinpad Service Error" );
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray( msg );
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.TrackKeyDecrypt( (byte)index, msg_byte, encrypt_byte );
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            setLastError( "Pinpad Service Error" );
            return null;
        }
        return HexUtil.byteToHexString( encrypt_byte );
    }

    @Override
    public Bundle getTusn(String random) {
        Log.d( LOG_TAG, "getTusn: " + random );
        if ( random == null  ) {
            Log.e( LOG_TAG, "random == null" );
            setLastError( "Input Param Error" );
            return null;
        }
        if ( pinpad == null ) {
            Log.e( LOG_TAG, "pinpad == null" );
            setLastError( "Pinpad Service Error" );
            return null;
        }
        try {
            TusnInfo tusnInfo = pinpad.GetTusn( HexUtil.hexStringToByteArray( random ) );
            if ( tusnInfo == null ) {
                setLastError( "Pinpad Service Execute Error" );
                return  null;
            }
            Bundle bundle = new Bundle();
            bundle.putBoolean( PinpadConstants.TUSN_SUPPORT, tusnInfo.isSupportTusn() );
            bundle.putString( PinpadConstants.TUSN, tusnInfo.getTusn() );
            bundle.putString(PinpadConstants.TUSN_MAC, tusnInfo.getMac() );
            return bundle;
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            return null;
        }
    }

    private EnterPinCallback enterPinCallback = null;
    @Override
    public void enterPin(EnterPinCallback enterPinCallback, Bundle param) {
        Log.d( LOG_TAG, "enterPin: " + param );
        if ( enterPinCallback == null ) {
            Log.e( LOG_TAG, "enterPinCallback == null" );
            setLastError( "Input Param Error" );
            return;
        }
        this.enterPinCallback = enterPinCallback;
        if ( param == null ) {
            Log.e( LOG_TAG, "param == null" );
            enterPinFailed( "Input Param Error param == null" );
            return;
        }
        String pan = param.getString(PinpadConstants.PAN);
        int pinkey_index = param.getInt( PinpadConstants.PINKEY_INDEX, -1 );
        int amount = param.getInt( PinpadConstants.AMOUNT, -1 );
        int overtime = param.getInt( PinpadConstants.OVERTIME, -1 );
        int[] pin_length_limit = param.getIntArray( PinpadConstants.PIN_LENGTH_LIMITS );
        if ( pinkey_index < 0 ) {
            Log.e( LOG_TAG, "pinkey_index < 0" );
            enterPinFailed( "Key Index Error" );
            return;
        }
        if ( pan != null ) {
            byte[] pan_in_byte = pan.getBytes();
            byte[] subPanBlock = new byte[12];
            Arrays.fill( subPanBlock, ( byte )0x00 );
            int max_len = ( pan_in_byte.length - 1 >= 12 ) ? 12 : ( pan_in_byte.length - 1 );
            for ( int i = 0; i < max_len; i++ ) {
                subPanBlock[i] = pan_in_byte[pan_in_byte.length-1-max_len+i];
            }
            //Log.d( LOG_TAG, "subPanBlock: " + HexUtil.byteToHexString( subPanBlock ) );
            byte[] hexSubPanBlock = HexUtil.asciiNumArrayToHexByteArray( subPanBlock );
            byte[] standard_panblock = new byte[8];
            Arrays.fill( subPanBlock, ( byte )0x00 );
            System.arraycopy( hexSubPanBlock, 0, standard_panblock, 2, hexSubPanBlock.length );
            Log.d( LOG_TAG, "standard_panblock: " + HexUtil.byteToHexString( standard_panblock ) );
            try {
                int result = pinpad.SetPanBlock( standard_panblock );
                if ( 0 != result ) {
                    Log.e( LOG_TAG, "SetPanBlock Failed: " + result );
                    enterPinFailed( "Execute Error" );
                    return;
                }
            } catch (RemoteException e) {
                Log.e( LOG_TAG, "RemoteException: " + e );
                enterPinFailed( "Pinpad Service Error" );
                return;
            }
        }
        if ( amount > 0 ) {
            byte[] amount_in_byte = HexUtil.hexStringToByteArray( HexUtil.intToBcdString( amount, 12 ) );
            try {
                int result = pinpad.SetAmount( amount_in_byte );
                if ( 0 != result ) {
                    Log.e( LOG_TAG, "SetAmount Failed: " + result );
                    enterPinFailed( "Execute Error" );
                    return;
                }
            } catch (RemoteException e) {
                Log.e( LOG_TAG, "RemoteException: " + e );
                enterPinFailed( "Pinpad Service Error" );
                return;
            }
        }
        if ( pin_length_limit != null ) {
            byte[] pin_length_limit_in_byte = new byte[pin_length_limit.length];
            for ( int i = 0; i < pin_length_limit.length; i++ ) {
                pin_length_limit_in_byte[i] = ( byte )pin_length_limit[i];
            }
            try {
                int result = pinpad.SetPinLen( pin_length_limit_in_byte );
                if ( 0 != result ) {
                    Log.e( LOG_TAG, "SetPinLen Failed: " + result );
                    enterPinFailed( "Execute Error" );
                    return;
                }
            } catch (RemoteException e) {
                Log.e( LOG_TAG, "RemoteException: " + e );
                enterPinFailed( "Pinpad Service Error" );
                return;
            }
        }
        int main_key_index = getMainKeyIndex( pinkey_index );
        if ( main_key_index < 0 || main_key_index > MAX_KEY_INDEX ) {
            Log.e( LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX" );
            enterPinFailed( "Key Index Error" );
            return;
        }
        overtime = ( overtime > 0 ) ? overtime : 120000;
        byte[] track_key = new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

        try {
            int result = pinpad.EnterPin( new PosdPinpadeEnterPinListener(), ( byte )main_key_index,
                            PinModeEnum.ISO9564, ( byte )main_key_index, track_key, overtime );
            if ( 0 != result ) {
                Log.e( LOG_TAG, "EnterPin Failed: " + result );
                enterPinFailed( "Execute Error" );
            }
        } catch (RemoteException e) {
            Log.e( LOG_TAG, "RemoteException: " + e );
            enterPinFailed( "Pinpad Service Error" );
        }
    }

    private void enterPinFailed(final String error) {
        if ( enterPinCallback != null ) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    enterPinCallback.onFail( error );
                }
            });
        }
    }

    private class PosdPinpadeEnterPinListener extends PinpadEnterPinListener.Stub {

        @Override
        public void OnSuccess(final byte[] enc, final int enc_len) throws RemoteException {
            if ( enterPinCallback == null ) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    byte[] data = new byte[enc_len];
                    System.arraycopy( enc, 0, data, 0, enc_len );
                    int i;
                    for ( i = 0; i < enc_len; i++ ) {
                        if ( data[i] != (byte)0xff ) {
                            break;
                        }
                    }
                    boolean is_no_pin = false;
                    if ( i >= enc_len ) {
                        is_no_pin = true;
                    } else {
                        is_no_pin = false;
                    }
                    enterPinCallback.onSuccess( HexUtil.byteToHexString( data ), is_no_pin );
                }
            });
        }

        @Override
        public void OnFail(int error_code) throws RemoteException {
            if ( error_code == -3 ) {
                enterPinFailed( "Enter Pin Time Over" );
            } else if ( error_code == -4 ) {
                enterPinFailed( "Enter Pin Canceled" );
            } else {
                enterPinFailed( "Enter Pin Error: " + error_code );
            }
        }

        @Override
        public void OnInput(final int input_num) throws RemoteException {
            if ( enterPinCallback == null ) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    enterPinCallback.onInput( input_num );
                }
            });
        }
    }
}
